<?php
/**
 * OM设备对象
 *
 * @version 0.1
 * @package OM\Dev;
 * @author  ZhangYe <banfg56@gmail.com>
 */
namespace OM\Dev;

use OM\OMadapter;
use OM\Dev\Ext;
use OM\Dev\Cdr;
use OM\Dev\Visitor;
use OM\Dev\Trunk;
use OM\Dev\Menu;
use OM\Lib\Apihelper;
use OM\Lib\Cache;
use OM\Api\Control;


class Om
{
	public $m_id = null;
	public $m_ipaddr = ''; //客户端连接websocket地址
	public $m_port   = '80';//电话外拨服务器端口
	public $m_server = ''; //电话外拨服务器IP地址
	public $m_mp3_server = ""; //录音服务器地址
	public $m_mp3_dir = ""; //OM 设备配置
	public $m_exts   = array(); //OM设备分级
	public $m_trunks = array(); //OM设备中继
	public $m_groups = array(); //OM设备分机分组
	public $m_states = null; //OM适配器状态

	public $m_dbhost = null ; //数据库IP地址
	public $m_dbport = "3306"; //数据库端口
	public $m_dbname = null; //数据库名称
	public $m_dbuser = null;//数据库访问用户名
	public $m_dbpsw  = ''; //数据库访问密码

	/**
	 * OM  设备信息
	 */
	private $m_mac = ''; //设备MAC地址
	private $m_version = '';//OM 版本
	private $m_model = ''; //模型
	private $m_manufacturer = '';//制造商

	//上次转接分机ID
	private $last_transfer_id ; //该适配器设备上一次转接的分机号


	private $om_api_url = "";

	const OM_RUNNING = 1;
	const OM_OFFLINE = 0;
	const OM_FORBINND = -1;

	public function __set($n,$v) {
		$var = 'm_'.$n;
		return $this->{$var} = $v;
	}

	public function __get($name) {
		$var = 'm_'.$name;
		if( !isset( $this->{$var} ) )
			return false;
		else
			return $this->{$var};
	}

	function  __construct( $_id, $_server, $_port = '80', $_mp3_server = '', $_mp3_dir = '')
	{
		$this->m_id = $_id;
		$this->m_server = $_server;
		$this->m_port = $_port;

		//mp3录音服务器地址配置
		$this->m_mp3_server = $_mp3_server;
		$this->m_mp3_dir = $_mp3_dir;

		$this->om_api_url = $this->m_server.':'.$this->m_port.'/xml';

		//从缓存中读取分级号
		//$data  = new Dev();

		$this->last_transfer_id = false;

		//从OM查询设备信息更新分级状态
		if( $this->initFromOM( true ) ){
			$this->m_states = self::OM_RUNNING;
		}
		else
			$this->m_states = self::OM_OFFLINE; //OM适配器对应的OM设备配置错误或者是OM出错
	}

	//从OM接口中更新设备信息
	protected function initFromOM( $forece = false )
	{
		/**
		 * fix start too slow,set timeout in 2 seconds
		 *
		 * @date 21/10/2016
		 * @author  ZhangYe
		 * @@fix  直接GET不带参数访问IPPBX报错问题
		 */
		$fix = Apihelper::curl_content( $this->om_api_url.'?method=gw.config.get&id=1', 2);
		if( !$fix ){
			OMadapter::log( OMadapter::LOG_WARN."OM:".str_pad(5,$this->m_id)." addr".str_pad($this->m_server,20)."\t is OFFLINE");
			return false;
		}
		$apiCtrl = new Control();
		$qdata = $apiCtrl->queryDevice( $this );
		if( $qdata ){
			foreach( $qdata as $kd=>$vd ){
				$this->{$kd} = $vd;
			}
			$this->initFromCRM( false );
			return true;
		}
		else{
			OMadapter::log( OMadapter::LOG_ERR.' OM:'.$this->m_id.' addr:'.$this->om_api_url." \t Init Error");
			return false;
		}
	}

	public static function getOmList( $update = false)
	{
		$crmURL = OMadapter::$OM_CRM_URL.'index.php?s=/Home/Om/getOms';
		$cnt = Apihelper::curl_content( $crmURL );
		if( $cnt && ($jres=json_decode( $cnt, true))!== false ){
			if( $jres['rcode'] )
				return $jres['data'];
		}
		return false;
	}

	/**
	 *  从CRM中更新分机状态信息
	 */
	protected function initFromCRM( $forece = false )
	{
		$crmURL = OMadapter::$OM_CRM_URL.'index.php?s=/Home/Om/getOmSubs&omID='.$this->m_id;
		$cnt = Apihelper::curl_content( $crmURL );
		if( $cnt && ($jres=json_decode( $cnt, true))!== false ) {
			if( $jres['rcode'] && $jres['data'] ){
				foreach( $jres['data'] as $vj){
					if( isset($this->m_exts[$vj['subphone']]) )
						$this->m_exts[$vj['subphone']]->crm_uid = $vj['admin_no'];
				}
				return true;
			}
		}
		return false;
	}

	/**
	 * 从IPX设备中重新加载配置信息(分机号与线路号信息),
	 * 如果发生变化通知客户端并上报,根据线路号进行判断
	 *
	 */
	public function omUpdate( $forece = true ){
		if( $this->m_states != self::OM_RUNNING )
			return false;
		$apiCtrl = new Control();
		$qdata = $apiCtrl->queryDevice( $this );
		if( $qdata && isset( $qdata['exts'] ) ){
			$arrOldExts = array();
			foreach( $this->m_exts as $vk=>$vv ){
				$arrOldExts[$vv->lineid ] = $vk;
			}
			$res = array('rcode'=>1,'op'=>Ext::API_CHANGE ,'data'=>null );
			foreach( $qdata['exts'] as $vext ){
				if( $arrOldExts[$vext->lineid] !=  $vext->id ){
					$res['data'] = $vext->id;
					$this->notifyExt( $arrOldExts[$vext->lineid], $res);
					$this->m_exts[$vext->id] =  $this->m_exts[$arrOldExts[$vext->lineid]];
					$this->m_exts[$vext->id]->id = $vext->id;
					unset( $this->m_exts[$arrOldExts[$vext->lineid]] );
				}
			}
			//CDR信息上报
			$crmURL = OMadapter::$OM_CRM_URL.'index.php?s=/Home/Om/report';
			$om_data = array();
			$om_data['omID'] = $this->m_id;
			$om_data['exts'] = array();
			foreach( $this->m_exts as $vk => $vv){
				$om_data['exts'][$vk] = $vv->lineid;
			}
			$cnt = Apihelper::curl_content( $crmURL, $om_data);
			if( $cnt && ($jres=json_decode( $cnt, true))!== false ) {
				if( $jres['rcode']){
					OMadapter::log( OMadapter::LOG_NOTICE." OM:".$this->m_id." 上报分机成功。");
				}
			}
			else{
				OMadapter::log( OMadapter::LOG_ERR." OM:".$this->m_id." 上报分机失败。detail:".json_encode( $arrOldExts ));
			}
			$arrOldExts  = null;
			$om_data = null;
			return true;
		}

		return false;
	}


	/**
	 * 获取指定分机状态
	 *
	 * @param  int $status  分机状态
	 * @return array/false
	 */
	public function getExts( $status = Ext::EXT_READY )
	{
		$arr = array();
		foreach( $this->m_exts as $kext=>$vext){
			if(  $vext->state == $status )
				$arr[] = $kext;
		}
		return $arr;
	}

	/**
	 * 缓存OM设备对象
	 *
	 * @return boolean  缓存设置成功或者失败
	 */
	public function saveCache()
	{
		$data = array();
		$key = $this->m_id.'_om';
		return Cache::add( $key, $data );
	}

	/**
	 * 从缓存中获取缓存的OM设备对象信息
	 *
	 * @return Om/false   成功获取缓存返回缓存对象，失败返回false
	 */
	public function readCache()
	{
		$cachedata = null;
		$key = $this->m_id.'_om';
		$cachedata = Cache::read( $key );

		return $cachedata;
	}

	/**
	 * 来电转接控制逻辑
	 *
	 * @param Vistor $_visitor 来电信息对象
	 *
	 * @return boolean  来电转接结果
	 */
	public function ExtVisitorTransfer( $_visitor )
	{
		if( !$_visitor || !isset( $_visitor['from'] ))
			return false;
		else{
			$toExt = null;
			$ws_state = 0 ;//
			$last_id = null;

			foreach( $this->m_exts as $kext=>$vext){
				$toExt = '243'; //默认转接给武学成
				break;
				if( $vext->state == 1 && $vext->crm_uid ){
					$find = false;
					if( !$this->last_transfer_id )
						$find = true;
					if( $this->last_transfer_id && ( $vext->state == 1  && $this->last_transfer_id == $last_id) )
						$find = true;
					if( $find ){
						$this->last_transfer_id = $kext;
						$toExt = $kext;
						$ws_state = $vext->ws_state;
						break;
					}
				}
				$last_id = $kext;
			}
			if( !$toExt )
				$toExt = array_keys($this->m_exts)[0];
			$apiCtrl = new Control();
				$extInfo =  $apiCtrl->TransVisitor2Ext( $this, $_visitor['id'], $toExt);
			$res = array('rcode'=>0,'op'=>Ext::API_INCOMMING ,'data'=>null );
			if( $extInfo ){
				$res['rcode'] = 1;
				$res['data'] = $_visitor;
				$res['exts'] = $this->getExts(); //本设备空闲分机列表
			}
			//客户端在线，发送弹屏信息
			if( $ws_state ){
				$this->notifyExt( $toExt, $res);
			}
		}
	}

	/**
	 * 分机状态变化通知
	 *
	 * @param  string $_extID 分机号
	 * @param  array  $_data  通知的数据，请参考接口数据格式
	 * @return boolean        分机状态变化是否处理成功
	 */
	public function notifyExt( $_extID, $_data )
	{
		if( $_extID && $_data && is_array( $_data ) ){
			$this->sendDatatoExt( $_extID, $_data );
		}
		else
			return true;

	}

	/**
	 * 向websocket客户端发送数据
	 *
	 * @param  int    $_ext   分机号
	 * @param  string $_data  要发送的数据
	 * @return boolean        是否发送成功
	 */
	public function sendDatatoExt( $_ext, $_data )
	{
		if( !$_ext || !$_data || !isset( OMadapter::$om_devices[$this->m_id] ) )
			return false;
		return OMadapter::sendWs2Ext( $this->m_id, $_ext, json_encode( $_data) );
	}

	/**
	 * 客户端浏览器websocket链接主动发送控制消息解析
	 *
	 *
	 * @param  string        $_extID  分机号
	 * @param  string        $ws_data 发送过来的数据, 数据格式是json,请解析的时候判断json包数据是否完整
	 * @param  Tcpconnection $tcpConn TCP连接对象，如果要向客户端发送消息，可以用 send( 要发送的数据 )
	 * @return boolean       推送的消息解析成功或者失败
	 */
	public function notifyWebSocket( $_extID, $ws_data ,$tcpConn )
	{
		if( !$this->m_exts || !isset($this->m_exts[$_extID]) )
			return false;
		else
		{
			$apiCtrl = new Control();
			$arr = json_decode($ws_data, true);
			$extInfo = null;
			$res = array('rcode'=>0,'data'=>null,'msg'=>'');
			if($arr){

				if( Ext::API_PING  == $arr['op'] ){//客户端与服务器保持连接
					$extInfo = date('Y-m-d H:i:s');
				}
				else if(  Ext::API_DAIL == $arr['op'] ){//拨打外部电话
					$dbarr = array('host'=>'', 'user'=>'','psw'=>'' ,'db'=>'' , 'port'=>'' );
					$dbarr['host'] = $this->m_dbhost;
					$dbarr['user'] = $this->m_dbuser;
					$dbarr['psw'] =  $this->m_dbpsw;
					$dbarr['db'] =  $this->m_dbname;
					$dbarr['port'] =  $this->m_dbport;
					$res['area'] = Apihelper::getPhoneAddr( $arr['data'] , $dbarr );
					$dbarr = null;
					$extInfo =  $apiCtrl->Dail($this, $_extID ,$arr['data']);
				}
				else if( Ext::API_BDMSG == $arr['op'] ){
					$bddata = array();
					$bddata['rcode'] =1;
					$bddata['op'] = $arr['op'];
					$bddata['data'] = $arr['data'];
					$this->extBroadcast( $bddata );
					$extInfo = 1;
				}
				else if( Ext::API_CLEAR == $arr['op'] ){
					$vid = isset( $arr['vid']) ? trim( $arr['vid']) : false;
					$oid = isset( $arr['oid']) ? trim( $arr['oid']) : false;
					$extInfo = $apiCtrl->Clear( $this, $_extID, $vid, $oid);
				}
				elseif( Ext::API_INCOMMING == $arr['op'] ){ //来电
					$extInfo =  $apiCtrl->Incoming($this, $arr['data']);
				}
				elseif( Ext::API_TRANSFER == $arr['op']){ //电话转接
					if( isset( $arr['ext'] ) && $arr['ext'] == $_extID )
						$extInfo = 0;
					else{
						$ext = isset( $arr['ext']) ? trim( $arr['ext']) : false;
						$vid = isset( $arr['vid']) ? trim( $arr['vid']) : false;
						$outer = isset( $arr['outer'] ) ? trim( $arr['outer'] ) : false;
						if( $outer )
							$extInfo = $apiCtrl->TransVisitor2Outer($this, $vid, $outer);
							else
						$extInfo = $apiCtrl->TransVisitor2Ext($this, $vid, $ext );
					}
				}
				elseif(  Ext::API_MUTE == $arr['op'] ){ //话机静音设置
					$extInfo =  $apiCtrl->Mute($this,$_extID);
				}
				elseif( Ext::API_GETLIST  == $arr['op'] ){
					$extInfo = $this->getExts();
				}
				$res['msg'] = 'OK';
				$res['rcode'] = 1;
				$res['op'] = $arr['op'];
				$res['data'] = $extInfo ;
			}
			else
				$res['msg'] = '数据解析失败';

			$tcpConn->send( json_encode( $res ) );
		}
	}

	/**
	 * 根据OM推送的xml解析消息
	 *
	 * @param  string  $strXml   OM推送的XML字符串
	 * @return boolean 消息是否处理成功
	 */
	public function notifyParse( $strXml )
	{
		$dbarr = array('host'=>'', 'user'=>'','psw'=>'' ,'db'=>'' , 'port'=>'' );
		$dbarr['host'] = $this->m_dbhost;
		$dbarr['user'] = $this->m_dbuser;
		$dbarr['psw'] =  $this->m_dbpsw;
		$dbarr['db'] =  $this->m_dbname;
		$dbarr['port'] =  $this->m_dbport;

		if( !$strXml || !preg_match('#^<#isU', $strXml )){
			OMadapter::log("OM推送消息错误，不是xml。data:".$strXml);
			return false;
		}
		$objXml = simplexml_load_string( $strXml );
		if( false === $objXml ){
			OMadapter::log("OM推送消息错误，xml解析失败。data:".$strXml);
			return false;
		}
        $notifydata = self::_parseNotify( $objXml, $dbarr );
        if( $notifydata ){
        	if( 'event' == $notifydata->type ){
        		if( 'incoming' == $notifydata->attr || 'invite' == $notifydata->attr){
        			$this->ExtVisitorTransfer( $notifydata->visitor);
        		}
        		elseif( 'config_change' == $notifydata->attr){
        			$this->omUpdate( true );
        		}
        		elseif( isset( $notifydata->extID) ){
        			$_extdata['op'] = $notifydata->attr;
        			$_extdata['data'] = (array)$notifydata;
        			$this->sendDatatoExt( $notifydata->extID, $_extdata);
        		}
        	}
        	elseif('cdr' == $notifydata->type ){
        		if( $notifydata->data ){
					$phone = ($notifydata->data['m_extid'] == $notifydata->data['m_cpn']) ? $notifydata->data['m_cdpn'] :  $notifydata->data['m_cpn'];
					$notifydata->data['area'] = Apihelper::getPhoneAddr( $phone , $dbarr );
					//只有正常的电话号码通讯才上传
					if( strlen( $phone )>7 ){
	        			//向客户端发送数据
	        			if( $notifydata->data['m_extid']){
	        				$res = array('op'=>Ext::API_CDR,'data'=> $notifydata->data,'rcode'=>1);
	        				$this->sendDatatoExt( $notifydata->data['m_extid'] , $res);
	        				$res = null;
	        			}
	        			$phone = null;
	        			//cdr上报处理
	        			$reportData = array();
	        			$reportData['cdr'] =$notifydata->data;
	        			$reportData['omID'] = $this->m_id;
	        			OMadapter::log( json_encode( $reportData ) );
	        			$repres = Apihelper::CDR( OMadapter::$OM_CRM_URL.'index.php?s=/Home/Om/cdr', $reportData );
	        			if( !$repres || !$repres['rcode'] ){
	        				$cdrlogs = "CDR 上传失败:".json_encode( $reportData );
	        				OMadapter::log( $cdrlogs );
	        			}
	        			else
	        				return true;
        			}
        			$phone = null;
        		} //notifydata->data
        	}//cdr
        }
		return false;
	}

	/**
	 * 解析OM推送的Event事件
	 *
	 * @param  Xmlelement $_xmlObj 消息对象xml
	 * @return false/array         返回错误或者是消息内容
	 */
	public static function _parseNotify( $_xmlObj, $dbarr = null )
	{
		// $_xmlObj instanceof SimpleXMLElement
		if( !$_xmlObj || !is_object($_xmlObj) )
			return false;
		$evData = new \stdClass();
		$evData->attr = isset($_xmlObj['attribute']) ? strtolower( trim( $_xmlObj['attribute']) ) : false;
		$evData->type = strtolower( $_xmlObj->getName() );
		if( !$evData->type )
			return false;
		if( 'event' == $evData->type ){
			switch( $evData->attr ){
				case 'busy':
				case 'idle':
					$tmp = $_xmlObj->ext->attributes();
					$evData->ext = reset( $tmp );
					$evData->extID = $evData->ext['id'];
				break;
				case 'ring':
				case 'alert':
				case 'answer':
				case 'answered':
				case 'bye':
				case 'devert':
				case 'transent':
					//分机之间的通话的事件，处理
					if( count( $_xmlObj->ext )>1 ){
						$evData->type = false;
					}
					else{
						if( isset( $_xmlObj->ext ) ){
							$tmp = $_xmlObj->ext->attributes();
							$evData->ext = reset( $tmp );
							$evData->extID = $evData->ext['id'];
						}
						if( isset( $_xmlObj->visitor ) ){
							$tmp = $_xmlObj->visitor->attributes();
							$evData->visitor = reset( $tmp );
						}
						if( isset( $_xmlObj->outer  ) ){
							$tmp = $_xmlObj->outer->attributes();
							$evData->outer  = reset( $tmp );
						}
						if( isset( $_xmlObj->menu  ) ){
							$tmp = $_xmlObj->menu->attributes();
							$evData->menu  = reset( $tmp );
						}
					}
				break;
				case 'failed':
					if( count( $_xmlObj->ext )>1 ){
						$tmp = $_xmlObj->menu->attributes();
						$evData->ext  = reset( $tmp );
					}
					else
						$evData->ext = $_xmlObj->ext->attributes();
					if(  isset( $$_xmlObj->err ) ){
						$tmp = $_xmlObj->err->attributes();
						$evData->err = reset( $tmp );
					}
					else
						$evData->err = false;
					if( $evData->err && isset( $evData->err['code']) )
						$evData->err['msg'] = Ext::failedMsg( $evData->err['code'] );
					else{
						if( !isset( $evData->err) || false === $evData->err )
							$evData->err = array();
						$evData->err['msg'] = json_encode( $_xmlObj );
					}
				break;
				case 'invite':
				case 'incoming':
					$tmp = $_xmlObj->trunk->attributes();
					$evData->trunk = reset( $tmp);
					if( isset($_xmlObj->visitor )  ){
						$tmp = $_xmlObj->visitor->attributes();
						$evData->visitor = reset( $tmp );
					}
					else
					$evData->visitor =false;
					if( $evData->visitor && isset( $evData->visitor['from'] ) && strlen( $evData->visitor['from'])>7 ){
						if( $dbarr )
							$evData->visitor['area'] = Apihelper::getPhoneAddr( $evData->visitor['from'], $dbarr);
					}
				break;
				case 'dtmf'://dtmf
				break;
				case 'bootup': //系统启动
				case 'config_change'://配置更改
				break;
			}
		}
		elseif( 'cdr' == $evData->type ){
			$cdr = new Cdr( );
			$cdr->xmlInit( $_xmlObj);
			$evData->data = $cdr->v2arr();
			$evData->data['tm'] = date('Y-m-d H:i:s');
		}
		return $evData;
	}
	/**
	 * 客户端链接服务器的时候，服务器向客户端发送的初始化数据.主要是分机状态与统计信息
	 *
	 * @param string  $_extID  分机编号
	 * @param string  $_data   客户端向服务器发送的数据
	 *
	 * @return false/array    分机不存在/服务器中记录的分机信息(未接电话、空闲时间、通话状态)
	 */
	public function InitExt2Ws( $_extID, $_data )
	{
		if( !$this->m_exts || !isset($this->m_exts[$_extID]) )
			return false;
		else
		{
			$apiCtrl = new Control();
			$extInfo =  $apiCtrl->queryExt( $this, $_extID );
			$res = array('rcode'=>0,'op'=>Ext::API_INIT ,'data'=>null );
			if( $extInfo){
				foreach( $extInfo as $kext =>$vext ){
					$this->m_exts[$_extID]->$kext = $vext;
				}
				$res['rcode'] = 1;
				$res['data'] = $extInfo;
			}
			else
				$res['msg'] = "查询设备信息失败";
			return $res;
		}
	}

	public function wsOffLineExt( $_extID )
	{
		if( $this->m_exts && isset($this->m_exts[$_extID])  )
		{
			$this->m_exts[$_extID]->ws_state = 0;
			return true;
		}
		else
			return false;
	}
	/**
	 * 向连接客户端发送广播消息
	 *
	 */
	public function extBroadcast( $data )
	{
		if( !$data )
			return false;
		//遍历适配器已经连接的客户端
		$omID = $this->m_id;
		return OMadapter::sendWsBroadCast( $this->m_id, $data );
	}

	/**
	 *  OM api 调用代理程序
	 *
	 *
	 */
	public function Apiagent( $_xml )
	{
		if( !$this->om_api_url || !$_xml ||false === simplexml_load_string($_xml) )
			return false;
		else
		{
			return Apihelper::curl_content( $this->om_api_url, 50, $_xml );
		}
	}
}

?>